import java.util.ArrayList;
import java.util.List;

public class Chain {
    int difficulty = 6; // 挖矿的难度，体现为计算的哈希值开头有多少位为0
    List<Block> chain = new ArrayList<>(); // 所有区块组成的链 (即Blocks）
    List<Transaction> transactionsPool = new ArrayList<>(); // 已经产生的交易，但是还未收录进区块中
    int minerReward = 50;// 出块奖励

    public Chain() {
        Block genesisBlock = bigBang();
        chain.add(genesisBlock);
    }

    // 创世：为当前链chain创建创世区块genesisBlock
    public Block bigBang() {
        List<Transaction> copyTransactionsPool = null; // 深拷贝
        try {
            copyTransactionsPool = Utils.deepCopyTransactions(this.transactionsPool);
        } catch (CloneNotSupportedException e) {
           e.printStackTrace();
        }
        Block genesisBlock = new Block(copyTransactionsPool, "创世区块没有prevHash");
        genesisBlock.hash = genesisBlock.getBlockHash();
        return genesisBlock;
    }

    // 获取链chain中的最新的一个区块
    public Block getLatestBlock() {
        return chain.get(chain.size() - 1);
    }

//    // 添加区块到区块链上
//    public void addBlockToChain(Block newBlock) {
//        newBlock.prevHash = getLatestBlock().hash;
//        // newBlock.hash = newBlock.computeHash();
//        newBlock.mine(difficulty); // 挖出一个符合难度的哈希值，在将新区块加入链中时，将该哈希值交给新区块作为其hash。这里是将挖区块（挖矿）简化为直接哈希，新区块是现成的
//        // 这个hash 需要满足一个区块链设置的条件
//        chain.add(newBlock);
//    }

    // addBlockToChain()并不合理，因为添加到链上的区块是从方法外部传入的，应该改为在方法内部生成这个区块：
    public void mineBlock(String minerAddress) throws Exception { // 可以理解为完整的挖矿“线程”
        // 新建一笔交易
        Transaction rewardTX = new Transaction("出块交易无来源方", minerAddress, this.minerReward); //出块奖励的发放也以交易的形式发放
        ArrayList<Transaction> txPoolForMiner= new ArrayList<>();
        txPoolForMiner.add(rewardTX);

        // 生成一个区块
        Block newBlock = new Block(txPoolForMiner, getLatestBlock().hash);

        // 执行挖矿
        newBlock.mine(difficulty);

        // 将新区块添加到链上
        chain.add(newBlock);
    }

    // 验证链的所有哈希。验证的角度有两种：交易的签名、区块的根哈希
    public boolean validateChain() throws Exception {
        // if(chain.size()==1){ //对于只有创世区块的区块链
        //     if(chain.get(0).hash.equals(chain.get(0).getBlockHash())){
        //         return false;
        //     }
        //     return true;
        // }
        for(int i = 1; i<= chain.size()-1; i++){
            Block blockToValidate = chain.get(i);
            if (!blockToValidate.validateBlockTransactions()){ // 验证链上区块的所有交易的签名
                System.out.println("链上某区块的某交易签名存在异常");
                return false;
            }
            if(!blockToValidate.hash.equals(blockToValidate.getBlockHash())){ // 二次计算区块的块头哈希。本质是在验证当前区块的所有交易的哈希
                System.out.println("区块根哈希验证不通过");
                return false;
            }
            Block prevBlock = chain.get(i-1);
            if(!blockToValidate.prevHash.equals(prevBlock.hash)){ //验证当前区块的prevHash是否等于prevBlock的hash
                System.out.println("前后区块连接异常");
                return false;
            }
        }
        return true;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("Chain{").append("\n");
        sb.append("    difficulty=").append(difficulty).append("\n");
        sb.append("    blocks[").append("\n");
        for(Block block : chain){
            sb.append("         ").append(block.toString()).append("\n");
        }
        sb.append("     ]").append('\n').append("}");
        return sb.toString();
    }

    public void addTxToPool(Transaction tx) throws Exception {
        // 在将交易收录到交易池前，先检查这个交易的合法性(签名验证）
        if(!tx.validate()){
            System.out.println("addTxToPool() Error: 不合法的交易！无法添加到交易池中");
        }
        transactionsPool.add(tx);
    }

    // test
    public Block createBlock() throws CloneNotSupportedException { // 在方法内手动创建一个区块，该区块的交易记录使用Chain的transactionsPool
        // 注意这个方法是不合理的，直接创建出来的区块是没有出块交易的
        List<Transaction> copyTransactionsPool = Utils.deepCopyTransactions(this.transactionsPool); // 深拷贝
        Block newBlock = new Block(copyTransactionsPool, this.getLatestBlock().hash);// 当成功通过transactionsPool新建了一个Block，transactionsPool应该被清空
        chain.add(newBlock);
        this.transactionsPool.clear();
        return newBlock;
    }

    public int getDifficulty() {
        return difficulty;
    }
    public void setDifficulty(int difficulty) {
        this.difficulty = difficulty;
    }
    public int getMinerReward() {
        return minerReward;
    }
    public void setMinerReward(int minerReward) {
        this.minerReward = minerReward;
    }
}